#include "query_pkg.h"
#include "assert.h"
#include <string>
#include "const.h"
#include "log_mgr.h"
#include "my_sql_parse.h"

extern Config g_stConfig;

QueryPkg::QueryPkg()
{
	;
}

QueryPkg::~QueryPkg()
{
	;
}

Result QueryPkg::Parse(Context* pCtx)
{
	assert(pCtx->pMysql->proto == REQ_QUERY);
	assert(pCtx->pMysql->pBuff);

	Result ret = OK;

	int iPkgLen = PKG_LEN(pCtx->pMysql->pBuff);

	if(pCtx->pMysql->dwLastReqSize == MAX_PKG_LEN)
	{
		//part of big pkg, save to query buffer;
		m_sQuery.append(pCtx->pMysql->pBuff+PKG_HEAD_LEN+1, iPkgLen-1);		
		pCtx->pMysql->dwLastReqSize = iPkgLen;
	}
	else
	{
		m_sQuery.append(pCtx->pMysql->pBuff+PKG_HEAD_LEN+1, iPkgLen-1);
		pCtx->pMysql->pSelf = this;
		//do sql parse
		ret = pCtx->pMysql->pParser->Parse(pCtx);
		pCtx->pMysql->dwLastReqSize = iPkgLen;
		m_sQuery.clear();
	}
	return ret;
}

//call back func
Result QueryPkg::Check(Context* pCtx)
{
	assert(pCtx);
	assert(pCtx->pMysql->pSelf == this);

#ifdef MASK_TEST
	return OK;
#endif
	Result ret = OK;
	CSqlParser* pSql = reinterpret_cast<CSqlParser*>(pCtx->pMysql->pParser);
	THD* thd = pSql->MyTHD();

	//check rule(white or black)
	if(g_stConfig.rule == R_WHITE)
	{
		assert(g_stConfig.rTable);
		if(g_stConfig.rTable->find(pSql->SqlSign())
			== g_stConfig.rTable->end())
		{
			ret = ERR_DROP;
			PKG_ERROR("[%s] [%s] [%s] [%s], [sql sign not in white list]", 
				retDesc[ret], 
				sqlCmdInfo[thd->lex->sql_command].name,
				m_sQuery.c_str(),
				pSql->SqlSign().c_str());
			return ret;
		}
	}
	else if(g_stConfig.rule == R_BLACK)
	{
		assert(g_stConfig.rTable);
		if(g_stConfig.rTable->find(pSql->SqlSign())
			!= g_stConfig.rTable->end())
		{
			ret = ERR_DROP;
			PKG_ERROR("[%s] [%s] [%s] [%s], [sql sign in black list]", 
				retDesc[ret], 
				sqlCmdInfo[thd->lex->sql_command].name,
				m_sQuery.c_str(),
				pSql->SqlSign().c_str());
			return ret;
		}
	}
	else
	{
		//default rule, check accept sql cmd id
		if(!(thd->lex->sql_command<SQLCOM_END && sqlCmdInfo[thd->lex->sql_command].isAccept))
		{
			ret = ERR_DROP;
			PKG_ERROR("[%s] [%s] [%s], [sql_cmd:%d not support]", 
				retDesc[ret], 
				pkgCmdInfo[CMD_NUM(pCtx->pMysql->pBuff)].name,
				m_sQuery.c_str(),
				thd->lex->sql_command);
			return ret;
		}
	}
	//check readonly
	bool bReadOnly = IsReadOnly(thd);
	if(!bReadOnly)
	{
		if(pCtx->pWorkDB->access == A_OR)
		{
			ret = ERR_DROP;
			PKG_ERROR("[%s] [%s] [%s], [db read only]", 
				retDesc[ret], 
				sqlCmdInfo[thd->lex->sql_command].name,
				m_sQuery.c_str());
			return ret;
		}
		
		if(g_stConfig.rule==R_DEFAULT && IsNoWhere(thd))
		{
			ret = ERR_DROP;
			PKG_ERROR("[%s] [%s] [%s], [update|delete with no where condition]", 
				retDesc[ret], 
				sqlCmdInfo[thd->lex->sql_command].name,
				m_sQuery.c_str());
			return ret;
		}
	}
	else
	{
		if(pCtx->pWorkDB->access == A_OW)
		{
			ret = ERR_DROP;
			PKG_ERROR("[%s] [%s] [%s], [db write only]", 
				retDesc[ret], 
				sqlCmdInfo[thd->lex->sql_command].name,
				m_sQuery.c_str());
			return ret;
		}
	}

	//log stat
	STAT("%s", pSql->SqlSign().c_str());
	
	PKG("[%s] [%s] [%s] [%s]", 
		retDesc[ret], 
		sqlCmdInfo[thd->lex->sql_command].name,
		bReadOnly?"read":"write",
		m_sQuery.c_str());
	
	return ret;
}

bool QueryPkg::IsNoWhere(THD* thd)
{
	if(((thd->lex->sql_command == SQLCOM_UPDATE)||(thd->lex->sql_command == SQLCOM_DELETE))
		&& (thd->lex->select_lex.where == NULL))
	{
		return true;
	}
	return false;
}

bool QueryPkg::IsReadOnly(THD* thd)
{
	assert(thd);
	
	LEX* lex = thd->lex;
	
	if( !(sql_command_flags[lex->sql_command] & CF_CHANGES_DATA))
		return true;
	
	const bool create_temp_tables= 
		(lex->sql_command == SQLCOM_CREATE_TABLE) &&
		(lex->create_info.options & HA_LEX_CREATE_TMP_TABLE);
	
	const bool drop_temp_tables= 
		(lex->sql_command == SQLCOM_DROP_TABLE) &&
		lex->drop_temporary;
	
	bool some_non_temp_table_updated = false;
	for (TABLE_LIST *table= lex->query_tables; table; table= table->next_global)
	{
		if(table->updating &&
		  !find_temporary_table(thd, table->db, table->table_name))
				some_non_temp_table_updated = true;
	}
	
	const bool update_real_tables=
		some_non_temp_table_updated && !(create_temp_tables || drop_temp_tables);
		
	const bool create_or_drop_databases=
		(lex->sql_command == SQLCOM_CREATE_DB) ||
		(lex->sql_command == SQLCOM_DROP_DB);

	if(update_real_tables || create_or_drop_databases)
	{
		  /*
			An attempt was made to modify one or more non-temporary tables.
		  */
		  return false;
	}
	
	return true;	
}


